package com.quectel.core.module.partyeducationtraining.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.quectel.constant.core.partyeducationtraining.TrainingItemConstants;
import com.quectel.constant.global.SystemConstants;
import com.quectel.util.aspect.lock.annotation.Lock;
import com.quectel.core.constants.RedisCacheConstants;
import com.quectel.core.constants.RedisLockConstants;
import com.quectel.core.module.partybuilding.dto.PartyOrganizationDto;
import com.quectel.core.module.partybuilding.service.PartyOrganizationService;
import com.quectel.core.module.partyeducationtraining.dao.TrainingItemPersonDao;
import com.quectel.core.module.partyeducationtraining.dto.TrainingDto;
import com.quectel.core.module.partyeducationtraining.dto.TrainingItemDto;
import com.quectel.core.module.partyeducationtraining.dto.TrainingItemPersonDto;
import com.quectel.core.module.partyeducationtraining.dto.TrainingPersonDto;
import com.quectel.core.module.partyeducationtraining.dto.ref.TrainingItemRefConcourseItemDto;
import com.quectel.core.module.partyeducationtraining.dto.ref.TrainingItemRefPersonDto;
import com.quectel.core.module.partyeducationtraining.entity.TrainingItemPersonEntity;
import com.quectel.core.module.partyeducationtraining.service.TrainingItemPersonService;
import com.quectel.core.module.partyeducationtraining.service.TrainingItemService;
import com.quectel.core.module.partyeducationtraining.service.TrainingPersonService;
import com.quectel.core.module.partyeducationtraining.service.TrainingService;
import com.quectel.util.common.CopyUtils;
import com.quectel.util.ex.CheckErrorException;
import com.quectel.util.kit.CacheKit;
import com.quectel.util.kit.Snowflake;
import com.quectel.util.mybatis.MyBatisPlusUtils;
import org.apache.dubbo.config.annotation.DubboService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

/**
 * @author ryan
 * @email ryan.ran@quectel.com
 * @date 2022/05/07 16:40
 */
@DubboService
public class TrainingItemPersonServiceImpl implements TrainingItemPersonService {

    private static final Logger LOGGER = LoggerFactory.getLogger(TrainingItemPersonServiceImpl.class);

    /**
     * 入参解释:实体id
     */
    private static final Function<Long, String> TRAINING_ITEM_PERSON_CACHE_BY_ID_FUNC = id -> String.format(RedisCacheConstants.PROJECT_NAME + "TRAINING_ITEM_PERSON_CACHE_BY_ID:%d", id);

    @Autowired
    private TrainingItemPersonDao trainingItemPersonDao;
    @Autowired
    private PartyOrganizationService partyOrganizationService;
    @Autowired
    private TrainingItemService trainingItemService;
    @Autowired
    private TrainingService trainingService;
    @Autowired
    private TrainingPersonService trainingPersonService;

    @Override
    public TrainingItemPersonDto selectOne(Long trainingItemId, Long deptPersonId) {
        TrainingItemPersonEntity trainingItemPersonEntity = trainingItemPersonDao.selectOne(new LambdaQueryWrapper<TrainingItemPersonEntity>()
                .eq(TrainingItemPersonEntity::getTrainingItemId, trainingItemId)
                .eq(TrainingItemPersonEntity::getPartyPersonId, deptPersonId));
        return CopyUtils.copyObj(trainingItemPersonEntity, TrainingItemPersonDto.class);
    }

    @Override
    @Lock(lockExpresses = RedisLockConstants.TRAINING_LOCK + "#{#trainingId}")
    public void study(Long trainingId, Long trainingItemId, Long deptPersonId, Date dateTime, BigDecimal studyProgress) {

        TrainingItemDto trainingItemCacheDto = trainingItemService.selectCacheById(trainingItemId);

        TrainingItemPersonDto dbTrainingItemPersonDto = selectOne(trainingItemId, deptPersonId);

        if (dbTrainingItemPersonDto == null) {
            // 开始学习
            TrainingItemDto trainingItemDto = trainingItemService.selectCacheById(trainingItemId);

            TrainingItemPersonDto newlyDto = new TrainingItemPersonDto();
            newlyDto.setTenantId(trainingItemDto.getTenantId());
            newlyDto.setPartyId(trainingItemDto.getPartyId());
            newlyDto.setTrainingItemId(trainingItemDto.getId());
            newlyDto.setPartyPersonId(deptPersonId);
            newlyDto.setStudyStatus(TrainingItemConstants.StudyStatusEnum.LEARNING.getStudyStatus());
            newlyDto.setStudyProgress(BigDecimal.ZERO);
            newlyDto.setStartTime(dateTime);
            save(newlyDto);

            TrainingPersonDto dbTrainingPersonDto = trainingPersonService.selectOne(trainingItemCacheDto.getTrainingId(), deptPersonId);
            if (dbTrainingPersonDto.getStudyStartTime() == null) {
                // 该人员第一次学习该课程
                TrainingPersonDto startTrainingPersonDto = new TrainingPersonDto();
                startTrainingPersonDto.setId(dbTrainingPersonDto.getId());
                startTrainingPersonDto.setStudyStartTime(dateTime);
                trainingPersonService.updateById(startTrainingPersonDto);
            }

            dbTrainingItemPersonDto = selectOne(trainingItemId, deptPersonId);
        }

        // 更新进度
        BigDecimal lastProgress = dbTrainingItemPersonDto.getStudyProgress() == null
                ? BigDecimal.ZERO : dbTrainingItemPersonDto.getStudyProgress();
        if (lastProgress.compareTo(studyProgress) == 1) {
            // 上报的学习进度比记录中的小 不刷新进度
            throw new CheckErrorException();
        }

        TrainingItemPersonDto itemUpdateDto = new TrainingItemPersonDto();
        itemUpdateDto.setId(dbTrainingItemPersonDto.getId());
        itemUpdateDto.setStudyStatus(TrainingItemConstants.StudyProgressEnum.ALL.getStudyProgress().compareTo(studyProgress) <= 0
                ? TrainingItemConstants.StudyStatusEnum.FINISH.getStudyStatus()
                : TrainingItemConstants.StudyStatusEnum.LEARNING.getStudyStatus());
        itemUpdateDto.setStudyProgress(studyProgress);
        itemUpdateDto.setEndTime(TrainingItemConstants.StudyProgressEnum.ALL.getStudyProgress().compareTo(studyProgress) <= 0
                ? dateTime
                : null);
        updateById(itemUpdateDto);


        if (TrainingItemConstants.StudyProgressEnum.ALL.getStudyProgress().compareTo(studyProgress) <= 0) {
            // 课时学习完成,涉及完成时关系更新
            relevantUpdate(trainingId, trainingItemId, deptPersonId, dateTime);
        }
    }

    /**
     * 完成一门课时的学习，可能涉及到的关系更新
     * <p>
     * 完成时
     * 1.课时(更新课时完成数)
     * 2.课程人员（更新完成进度、完成数，完成最后一门课时，更新完成时间）
     * 3.课程（完成最后一门课时，更新课程完成数）
     */
    @Lock(lockExpresses = RedisLockConstants.TRAINING_LOCK + "#{#trainingId}")
    private void relevantUpdate(Long trainingId, Long trainingItemId, Long deptPersonId, Date dateTime) {

        // 课程锁
        TrainingPersonDto dbTrainingPersonDto = trainingPersonService.selectOne(trainingId, deptPersonId);

        // 更新课时
        TrainingItemDto dbTrainingItemDto = trainingItemService.selectById(trainingItemId);
        TrainingItemDto newlyTrainingItemDto = new TrainingItemDto();
        newlyTrainingItemDto.setId(dbTrainingItemDto.getId());
        newlyTrainingItemDto.setCompleteCount(dbTrainingItemDto.getCompleteCount() == null
                ? 1 : dbTrainingItemDto.getCompleteCount() + 1);
        trainingItemService.updateById(newlyTrainingItemDto);

        // 更新课程人员
        TrainingDto dbTrainingDto = trainingService.selectById(trainingId);

        int nowCompleteItemCount = dbTrainingPersonDto.getCompleteItemCount() == null
                ? 1 : dbTrainingPersonDto.getCompleteItemCount() + 1;
        TrainingPersonDto newlyTrainingPersonDto = new TrainingPersonDto();
        newlyTrainingPersonDto.setId(dbTrainingPersonDto.getId());
        newlyTrainingPersonDto.setCompleteItemCount(nowCompleteItemCount);

        BigDecimal completeRate = new BigDecimal(String.valueOf(nowCompleteItemCount))
                .divide(new BigDecimal(String.valueOf(dbTrainingDto.getTrainingItemCount())), 2, RoundingMode.HALF_UP);

        // 完成率不应该超过100% 判断是担心溢出抛错
        newlyTrainingPersonDto.setCompleteRate(completeRate.compareTo(BigDecimal.ONE) == 1
                ? BigDecimal.ONE.multiply(BigDecimal.valueOf(100)).byteValueExact()
                : completeRate.multiply(BigDecimal.valueOf(100)).byteValueExact());

        if (nowCompleteItemCount >= dbTrainingDto.getTrainingItemCount()) {
            // 完成最后一门课时 更新课程
            TrainingDto newlyTrainingDto = new TrainingDto();
            newlyTrainingDto.setId(dbTrainingDto.getId());
            newlyTrainingDto.setTrainingCompletePersonCount(dbTrainingDto.getTrainingCompletePersonCount() == null ?
                    1 : dbTrainingDto.getTrainingCompletePersonCount() + 1);
            trainingService.updateById(newlyTrainingDto);

            // 更新课程人员
            newlyTrainingPersonDto.setStudyEndTime(dateTime);
        }

        trainingPersonService.updateById(newlyTrainingPersonDto);
    }

    @Override
    public int queryTrainingItemTotal(Map<String, Object> map) {
        return trainingItemPersonDao.queryTrainingItemTotal(map);
    }

    @Override
    public List<TrainingItemRefConcourseItemDto> queryTrainingItemList(Map<String, Object> map) {
        return trainingItemPersonDao.queryTrainingItemList(map);
    }

    @Override
    public List<TrainingItemRefPersonDto> queryPersonList(Map<String, Object> map) {
        List<TrainingItemRefPersonDto> itemPersonInfoDtos = trainingItemPersonDao.queryPersonList(map);
        for (TrainingItemRefPersonDto itemPersonInfoDto : itemPersonInfoDtos) {
            Long deptId = itemPersonInfoDto.getPartyId();
            PartyOrganizationDto partyOrganizationDto = partyOrganizationService.selectCacheById(deptId);
            itemPersonInfoDto.setParty(partyOrganizationDto);
        }
        return itemPersonInfoDtos;
    }

    @Override
    public int queryPersonTotal(Map<String, Object> map) {
        return trainingItemPersonDao.queryPersonTotal(map);
    }

    @Override
    public TrainingItemPersonDto selectCacheById(Long id) {
        return CacheKit.cacheToRedis(
                () -> selectById(id),
                TRAINING_ITEM_PERSON_CACHE_BY_ID_FUNC.apply(id),

                SystemConstants.NOT_NULL_CACHE_EXPIRE_SECONDS
        );
    }

    @Override
    public TrainingItemPersonDto selectById(Long id) {
        TrainingItemPersonEntity entity = trainingItemPersonDao.selectById(id);
        return paddingField(CopyUtils.copyObj(entity, TrainingItemPersonDto.class));
    }

    @Override
    public List<TrainingItemPersonDto> queryList(Map<String, Object> params) {
        List<TrainingItemPersonEntity> list = trainingItemPersonDao.queryList(params);
        List<TrainingItemPersonDto> result = CopyUtils.copyList(list, TrainingItemPersonDto.class);
        result.forEach(this::paddingField);
        return result;
    }

    private TrainingItemPersonDto paddingField(TrainingItemPersonDto dto) {
        if (dto != null) {

        }
        return dto;
    }

    @Override
    public int queryTotal(Map<String, Object> params) {
        return trainingItemPersonDao.queryTotal(params);
    }

    @Override
    public Long save(TrainingItemPersonDto dto) {
        TrainingItemPersonEntity entity = CopyUtils.copyObj(dto, TrainingItemPersonEntity.class);
        entity.setId(Snowflake.nextId());
        trainingItemPersonDao.insert(entity);
        return entity.getId();
    }

    @Override
    public void updateById(TrainingItemPersonDto dto) {
        TrainingItemPersonEntity entity = CopyUtils.copyObj(dto, TrainingItemPersonEntity.class);
        trainingItemPersonDao.updateById(entity);
        CacheKit.invalidRedisCache(TRAINING_ITEM_PERSON_CACHE_BY_ID_FUNC.apply(dto.getId()));
    }

    @Override
    public void updateAllColumnById(TrainingItemPersonDto dto) {
        TrainingItemPersonEntity entity = CopyUtils.copyObj(dto, TrainingItemPersonEntity.class);


        MyBatisPlusUtils.updateAllColumnById(entity, trainingItemPersonDao);

        CacheKit.invalidRedisCache(TRAINING_ITEM_PERSON_CACHE_BY_ID_FUNC.apply(dto.getId()));
    }

    @Override
    public void deleteById(Long id) {
        trainingItemPersonDao.deleteById(id);

        CacheKit.invalidRedisCache(TRAINING_ITEM_PERSON_CACHE_BY_ID_FUNC.apply(id));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteBatch(Long[] ids) {
        for (Long id : ids) {
            deleteById(id);
        }
    }
}
